/* $Id: AbstractGrid.java 137 2011-08-29 20:33:36Z altdotua@gmail.com $ */

package alt.djudge.frontend.client.ui.grids;

import java.util.Comparator;
import java.util.Collections;
import java.util.List;
import java.util.Vector;

import alt.djudge.frontend.client.rpc.DJudgeGWTAdminService;
import alt.djudge.frontend.client.ui.admin.Layout;
import alt.djudge.frontend.client.ui.editors.CustomEditor;
import alt.djudge.frontend.client.ui.editors.EditorsFactory;
import alt.djudge.frontend.shared.dto.admin.AdminDTOEntry;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.gwt.user.client.ui.VerticalPanel;


class GridHeaderWidget extends HorizontalPanel
{
	final Image imgSortAscending = new Image("/images/icons/s_asc.png");
	
	final Image imgSortDescending = new Image("/images/icons/s_desc.png");
	
	final MenuBar headerMenu;
	
	final MenuBar filterBar;
	
	final MenuItem filterItem;
	
	final int fieldIndex;
	
	@SuppressWarnings("rawtypes")
	final AbstractGrid grid;
	
	private List<FilterWrapper> filters = new Vector<FilterWrapper>(); 
	
	private DialogBox createFilterDialog;
	
	public GridHeaderWidget(@SuppressWarnings("rawtypes") AbstractGrid abstractGrid, int fieldIndex0, String title)
	{
		this.grid = abstractGrid;
		this.fieldIndex = fieldIndex0;
		setVerticalAlignment(ALIGN_MIDDLE);
		setSpacing(3);
		
		filterBar = new MenuBar(true);
		filterBar.addItem("Disable all", new Command()
		{
			@Override
			public void execute()
			{
				for (FilterWrapper filterWrapper : filters)
				{
					filterWrapper.setEnabled(false);
				}
				grid.showDataInGrid();
			}
		});
		filterBar.addItem("Enable all", new Command()
		{
			@Override
			public void execute()
			{
				for (FilterWrapper filterWrapper : filters)
				{
					filterWrapper.setEnabled(true);
				}
				grid.showDataInGrid();
			}
		});
		filterBar.addItem("Delete all", new Command()
		{
			@Override
			public void execute()
			{
				for (FilterWrapper filterWrapper : filters)
				{
					filterBar.removeItem(filterWrapper.menuItem);
				}
				filters.clear();
				grid.showDataInGrid();
			}
		});
		filterBar.addItem("Add filter...", new Command()
		{
			@Override
			public void execute()
			{
				if (null == createFilterDialog)
					createFilterDialog = createFilterDialogBox(GridHeaderWidget.this);
				createFilterDialog.setGlassEnabled(true);
				createFilterDialog.setAnimationEnabled(true);
				createFilterDialog.center();
				createFilterDialog.show();
			}
		});
		filterBar.addSeparator();
		
		filterItem = new MenuItem("Filtering", filterBar);
		
		headerMenu = new MenuBar();
		
		MenuBar headerSubMenu = new MenuBar(true);
		headerSubMenu.addItem("Sort ASC", new Command()
		{
			@Override
			public void execute()
			{
				grid.sortDataByField(fieldIndex, true);
			}
		});
		headerSubMenu.addItem("Sort DESC", new Command()
		{
			@Override
			public void execute()
			{
				grid.sortDataByField(fieldIndex, false);
			}
		});
		headerSubMenu.addSeparator();
		headerSubMenu.addItem(filterItem);
		
		headerMenu.addItem(new MenuItem(title, headerSubMenu));
		
		add(imgSortAscending);
		add(headerMenu);
		add(imgSortDescending);
		
		imgSortAscending.addClickHandler(new ClickHandler()
		{
			@Override
			public void onClick(ClickEvent event)
			{
				grid.sortDataByField(fieldIndex, true);
			}
		});
		
		imgSortDescending.addClickHandler(new ClickHandler()
		{
			@Override
			public void onClick(ClickEvent event)
			{
				grid.sortDataByField(fieldIndex, false);
			}
		});
	}

	public void addFilter(final Filter filter)
	{
		final FilterWrapper wrapper = new FilterWrapper(filter);
		final MenuItem menuItem = new MenuItem("", new Command()
		{
			@Override
			public void execute()
			{
				wrapper.setEnabled(!wrapper.getEnabled());
				grid.showDataInGrid();
			}
		});
		filterBar.addItem(menuItem);
		wrapper.menuItem = menuItem;
		wrapper.setEnabled(true);
		filters.add(wrapper);
		grid.showDataInGrid();
	}
	
	public boolean matches(AdminDTOEntry item)
	{
		boolean matches = true;
		String value = item.getFieldValue(fieldIndex);
		for (FilterWrapper filterWrapper : filters)
			if (filterWrapper.getEnabled() && !filterWrapper.filter.matches(value))
			{
				matches = false;
				break;
			}
		return matches;
	}
	
	private DialogBox createFilterDialogBox(final GridHeaderWidget header)
	{
		final DialogBox dialogBox = new DialogBox();
		dialogBox.setText("Add filter");

		VerticalPanel dialogContents = new VerticalPanel();
		dialogContents.setSpacing(2);
		dialogBox.setWidget(dialogContents);

		final CreateFilterPanel filterPanel = new CreateFilterPanel();
		dialogContents.add(filterPanel);

		
		final Button btnAddFilter = new Button("Add");
		btnAddFilter.addClickHandler(new ClickHandler()
		{
			@Override
			public void onClick(ClickEvent event)
			{
				header.addFilter(filterPanel.getFilter());
				dialogBox.hide();
			}
		});

		Button closeButton = new Button("Cancel");
		closeButton.addClickHandler(new ClickHandler()
		{
			public void onClick(ClickEvent event)
			{
				dialogBox.hide();
			}
		});
		
		HorizontalPanel panelButtons = new HorizontalPanel();
		panelButtons.add(btnAddFilter);
		panelButtons.add(closeButton);
		panelButtons.setSpacing(15);
		dialogContents.add(panelButtons);
		
		return dialogBox;
	}	
}

public abstract class AbstractGrid<T extends AdminDTOEntry> extends Composite
{
	@SuppressWarnings("rawtypes")
	interface MyUiBinder extends UiBinder<FlowPanel, AbstractGrid> {}

	private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
	
	@UiField
	FlowPanel panel;
	
	public List<T> receivedData = new Vector<T>();
	
	List<DTOEntryGridWrapper<T> > filteredData = new Vector<DTOEntryGridWrapper<T> >();
	
	Grid grid;
	
	private GridHeaderWidget[] headers;
	
	private CustomEditor[] editors;
	
	//abstract void loadData(int page);
	
	private T classInstance;
	
	private List<CheckBox> cbsVisible = new Vector<CheckBox>();
	
	private List<CheckBox> cbsActive = new Vector<CheckBox>();
	
	private List<CheckBox> cbsDeleted = new Vector<CheckBox>();
	
	//CheckBox headerCheckBox = new CheckBox();
	private MenuBar headerMenuBar = new MenuBar();
	
	Vector<CheckBox> checkBoxes = new Vector<CheckBox>();

	public AbstractGrid(T classInstance)
	{
		initWidget(uiBinder.createAndBindUi(this));
		
		this.classInstance = classInstance;
		
		initGrid();
		
		initClickHandlers();
	}
	
	void initClickHandlers()
	{
		/*headerCheckBox.addValueChangeHandler(new ValueChangeHandler<Boolean>()
		{
			@Override
			public void onValueChange(ValueChangeEvent<Boolean> event)
			{
				selectAll(event.getValue());
			}
		});*/
	}
	
	void selectAll(boolean selected)
	{
		for (CheckBox checkBox : checkBoxes)
		{
			checkBox.setValue(selected);
		}
		//headerCheckBox.setValue(selected);
	}
	
	private void initGrid()
	{
		String[] fieldNames = classInstance.getFieldNames();
		int fieldsCount = fieldNames.length;
		String[] actionNames = classInstance.getActionNames();
		int actionsCount = actionNames.length;
		
		int columnsTotal = fieldsCount + actionsCount + 4;
		grid = new Grid(1, columnsTotal);
		grid.setBorderWidth(1);
		
		headers = new GridHeaderWidget[fieldsCount + 3];
		editors = new CustomEditor[fieldsCount];
		
		int columnIndex = 0;
		grid.setWidget(0, columnIndex++, headerMenuBar);
		for (int fieldIndex = 0; fieldIndex < fieldsCount; fieldIndex++)
		{
			editors[fieldIndex] = EditorsFactory.getEditor(classInstance.getClass(), fieldIndex);
			headers[fieldIndex] = new GridHeaderWidget(this, fieldIndex, classInstance.getFieldNames()[fieldIndex]);
			grid.setWidget(0, columnIndex++, headers[fieldIndex]);
		}
		headers[fieldsCount] = new GridHeaderWidget(this, -1, "A");
		headers[fieldsCount+1] = new GridHeaderWidget(this, -2, "V");
		headers[fieldsCount+2] = new GridHeaderWidget(this, -3, "D");
		grid.setWidget(0, columnIndex++, headers[fieldsCount]);
		grid.setWidget(0, columnIndex++, headers[fieldsCount+1]);
		grid.setWidget(0, columnIndex++, headers[fieldsCount+2]);
		
		panel.add(grid);
		
		/* Header menu */
		MenuBar subMenu = new MenuBar(true);
		subMenu.addItem("Create new item...", new Command()
		{
			@Override
			public void execute()
			{
				final DTOEntryGridWrapper<T> wrappedEntry = new DTOEntryGridWrapper<T>(AbstractGrid.this, -1, -1, classInstance);
				EditEntryPanel<T> panel = new EditEntryPanel<T>(wrappedEntry, true);
				DialogBox dialog = Layout.createEditEntryDialogBox(panel);
				dialog.setGlassEnabled(true);
				dialog.setAnimationEnabled(true);
				dialog.center();
				dialog.show();
			}
		});
		subMenu.addSeparator();
		subMenu.addItem("Check all", new Command()
		{
			@Override
			public void execute()
			{
				selectAll(true);
			}
		});
		subMenu.addItem("Uncheck all", new Command()
		{
			@Override
			public void execute()
			{
				selectAll(false);
			}
		});
		subMenu.addSeparator();
		
		final MenuBar menuActive = new MenuBar();
		menuActive.addItem("True", new Command()
		{
			@Override
			public void execute()
			{
				for (int i = 0; i < checkBoxes.size(); i++)
					if (checkBoxes.get(i).getValue())
						cbsActive.get(i).setValue(true, true);
			}
		});
		menuActive.addItem("False", new Command()
		{
			@Override
			public void execute()
			{
				for (int i = 0; i < checkBoxes.size(); i++)
					if (checkBoxes.get(i).getValue())
						cbsActive.get(i).setValue(false, true);
			}
		});
		
		final MenuBar menuVisible = new MenuBar();
		menuVisible.addItem("True", new Command()
		{
			@Override
			public void execute()
			{
				for (int i = 0; i < checkBoxes.size(); i++)
					if (checkBoxes.get(i).getValue())
						cbsVisible.get(i).setValue(true, true);
			}
		});
		menuVisible.addItem("False", new Command()
		{
			@Override
			public void execute()
			{
				for (int i = 0; i < checkBoxes.size(); i++)
					if (checkBoxes.get(i).getValue())
						cbsVisible.get(i).setValue(false, true);
			}
		});

		final MenuBar menuDeleted = new MenuBar();
		menuDeleted.addItem("True", new Command()
		{
			@Override
			public void execute()
			{
				for (int i = 0; i < checkBoxes.size(); i++)
					if (checkBoxes.get(i).getValue())
					{
						cbsDeleted.get(i).setValue(true, true);
					}
			}
		});
		menuDeleted.addItem("False", new Command()
		{
			@Override
			public void execute()
			{
				for (int i = 0; i < checkBoxes.size(); i++)
					if (checkBoxes.get(i).getValue())
						cbsDeleted.get(i).setValue(false, true);
			}
		});
		
		subMenu.addSeparator();
		subMenu.addItem("Set active", menuActive);
		subMenu.addItem("Set deleted", menuDeleted);
		subMenu.addItem("Set visible", menuVisible);	
		
		headerMenuBar.addItem("+", subMenu);
	}
	
	void reportError(Throwable caught)
	{
		Window.alert(caught.toString());
	}
	
	public void showDataInGrid()
	{
		filteredData.clear();
		cbsActive.clear();
		cbsDeleted.clear();
		cbsVisible.clear();
		
		// filtering
		int entryIndex = 0;
		for (T item : receivedData)
		{
			boolean filtered = false;
			for (int i = 0; i < headers.length; i++)
				if (!headers[i].matches(item))
					filtered = true;
			if (!filtered)
			{
				DTOEntryGridWrapper<T> wrapper = new DTOEntryGridWrapper<T>(this, entryIndex++, filteredData.size(), item);
				filteredData.add(wrapper);
			}
		}
		

		grid.resizeRows(filteredData.size() + 1);
		checkBoxes.setSize(filteredData.size());
		
		String[] fieldNames = classInstance.getFieldNames();
		int fieldsCount = fieldNames.length;
		String[] actionNames = classInstance.getActionNames();
		int actionsCount = actionNames.length;
		
		for (int dataIndex = 0, row = 1; dataIndex < filteredData.size(); dataIndex++, row++)
		{
			final DTOEntryGridWrapper<T> wrappedEntry = filteredData.get(dataIndex); 
			final T entry = wrappedEntry.dtoEntry;
			
			int column = 0;
			CheckBox checkBox = new CheckBox();
			checkBoxes.set(dataIndex, checkBox);
			grid.setWidget(row, column++, checkBox);
			
			for (int fieldIndex = 0; fieldIndex < fieldsCount; fieldIndex++)
			{
				grid.setText(row, column++, editors[fieldIndex].getValueForKey(entry.getFieldValue(fieldIndex)));
			}
			
			final CheckBox cbVisible = new CheckBox();
			cbVisible.setValue(entry.isVisible());
			cbVisible.addValueChangeHandler(new ValueChangeHandler<Boolean>()
			{
				@Override
				public void onValueChange(ValueChangeEvent<Boolean> event)
				{
					entry.setVisible(event.getValue());
					String errorMessage = "Error toggling field `visible' on entry#" + entry.getId().toString();
					AsyncCallback<Boolean> callback = createAsyncCallbackBoolean(errorMessage);
					int field = DJudgeGWTAdminService.FIELD_VISIBLE;
					Layout.adminService.setEntryField(entry.getClass().getName(), entry.getId(), field, event.getValue(), callback);
				}
			});

			final CheckBox cbActive = new CheckBox();
			cbActive.setValue(entry.isActive());
			cbActive.addValueChangeHandler(new ValueChangeHandler<Boolean>()
			{
				@Override
				public void onValueChange(ValueChangeEvent<Boolean> event)
				{
					entry.setActive(event.getValue());
					String errorMessage = "Error toggling field `active' on entry#" + entry.getId().toString();
					AsyncCallback<Boolean> callback = createAsyncCallbackBoolean(errorMessage);
					int field = DJudgeGWTAdminService.FIELD_ACTIVE;
					Layout.adminService.setEntryField(entry.getClass().getName(), entry.getId(), field, event.getValue(), callback);
				}
			});

			final CheckBox cbDeleted = new CheckBox();
			cbDeleted.setValue(entry.isDeleted());
			cbDeleted.addValueChangeHandler(new ValueChangeHandler<Boolean>()
			{
				@Override
				public void onValueChange(ValueChangeEvent<Boolean> event)
				{
					entry.setDeleted(event.getValue());
					String errorMessage = "Error toggling field `deleted' on entry#" + entry.getId().toString();
					AsyncCallback<Boolean> callback = createAsyncCallbackBoolean(errorMessage);
					int field = DJudgeGWTAdminService.FIELD_DELETED;
					Layout.adminService.setEntryField(entry.getClass().getName(), entry.getId(), field, event.getValue(), callback);
				}
			});
			cbsActive.add(cbActive);
			cbsDeleted.add(cbDeleted);
			cbsVisible.add(cbVisible);
			
			grid.setWidget(row, column++, cbActive);
			grid.setWidget(row, column++, cbVisible);
			grid.setWidget(row, column++, cbDeleted);
			
			for (int actionIndex = 0; actionIndex < actionsCount; actionIndex++)
			{
				String actionName = actionNames[actionIndex];
				String[] ids = new String[] { entry.getId().toString() };
				Image actionWidget = ActionWidgetsFactory.getActionWidget(wrappedEntry.getClass(), actionName, ids);
				if ("edit".equalsIgnoreCase(actionNames[actionIndex]))
				{
					actionWidget.addClickHandler(new ClickHandler()
					{
						@Override
						public void onClick(ClickEvent event)
						{
							EditEntryPanel<T> panel = new EditEntryPanel<T>(wrappedEntry);
							DialogBox dialog = Layout.createEditEntryDialogBox(panel);
							dialog.setGlassEnabled(true);
							dialog.setAnimationEnabled(true);
							dialog.center();
							dialog.show();
						}
					});
				}
				else if ("copy".equalsIgnoreCase(actionNames[actionIndex]))
				{
					actionWidget.addClickHandler(new ClickHandler()
					{
						@Override
						public void onClick(ClickEvent event)
						{
							Layout.adminService.copyEntry(entry, new AsyncCallback<AdminDTOEntry>()
							{
								@SuppressWarnings("unchecked")
								@Override
								public void onSuccess(AdminDTOEntry result)
								{
									receivedData.add((T) result);
									showDataInGrid();
								}
								
								@Override
								public void onFailure(Throwable caught)
								{
									
								}
							});
						}
					});
				}
				grid.setWidget(row, column++, actionWidget);
			}
		}
	}
	
	AsyncCallback<Boolean> createAsyncCallbackBoolean(final String errorMessage)
	{
		return new AsyncCallback<Boolean>()
		{
			@Override
			public void onFailure(Throwable caught)
			{
				Window.alert(errorMessage + "\nDetails:\n" + caught.toString());
			}

			@Override
			public void onSuccess(Boolean result)
			{
				// nothing to do
			}
		};
	}
	
	void sortDataByField(int fieldIndex, boolean sortAscending)
	{
		Comparator<T> ascendingSortComparator = getFieldComparator(fieldIndex);
		Comparator<T> sortComparator = sortAscending ? ascendingSortComparator : Collections.reverseOrder(ascendingSortComparator);
		Collections.sort(receivedData, sortComparator);
		showDataInGrid();
	}
	
	@SuppressWarnings("unchecked")
	void onDataLoadedDTO(AdminDTOEntry[] data)
	{
		onDataLoaded((T[]) data);
	}
	
	void onDataLoaded(T[] data)
	{
		receivedData.clear();
		for (int i = 0; i < data.length; i++)
		{
			receivedData.add(data[i]);
		}
		showDataInGrid();
	}
	
	Comparator<T> getStringFieldComparator(int fieldIndex)
	{
		final int field = fieldIndex;
		return new Comparator<T>()
		{
			@Override
			public int compare(T o1, T o2)
			{
				String s1 = o1.getFieldValue(field);
				String s2 = o2.getFieldValue(field);
				if (null == s1)
					return s2 == null ? 0 : -1;
				return s1.compareToIgnoreCase(s2);
			}
		};
	}
	
	Comparator<T> getLongFieldComparator(int fieldIndex)
	{
		final int field = fieldIndex;
		return new Comparator<T>()
		{
			@Override
			public int compare(T o1, T o2)
			{
				String s1 = o1.getFieldValue(field);
				String s2 = o2.getFieldValue(field);
				Long i1 = Long.parseLong(s1);
				Long i2 = Long.parseLong(s2);
				return i1.compareTo(i2);
			}
		};
	}
	
	Comparator<T> getFieldComparator(int fieldIndex)
	{
		if (fieldIndex == 0)
			return getLongFieldComparator(0);
		return getStringFieldComparator(fieldIndex);
	}
	
	public void loadData(int page)
	{
		AsyncCallback<AdminDTOEntry[]> callback = new AsyncCallback<AdminDTOEntry[]>()
		{

			@Override
			public void onFailure(Throwable caught)
			{
				reportError(caught);
			}

			@Override
			public void onSuccess(AdminDTOEntry[] result)
			{
				onDataLoadedDTO(result);
			}
		};
		Layout.adminService.getEntries(classInstance.getClass().getName(), -1, callback);
	}
}
